using System;
using System.Collections.Generic;
using System.Net;
using System.Threading;
using SpoonCore.Message;
using SpoonCore.Handler.Core;
using SpoonCore.Message.Info.Core;

namespace SpoonCore.Handler
{
	public class CoreHandler : IMessageHandler
	{
		private SpoonClient _client;
		private readonly Random _rand = new Random();
		private ExchangeManager _exchangeManager;
		private PingManager _pingManager;

		private readonly SortedList<int, LookupItem> _lookupList = new SortedList<int, LookupItem>();

		private void LookupReceive(LookupMessage msg, ClientInfo destClient)
		{
			if (msg.Type == LookupMessage.LookupType.Request)
			{
				if (msg.SourceClient.Id == destClient.Id) msg.SourceClient.AddEndpoint(destClient.Endpoints);

				msg.AddHop();

				if (msg.SourceClient == null) msg.SourceClient = destClient;

				ClientInfo next = _client.RoutingTable.FindNext(msg.LookupId);
				if (next != null)
				{
					// Forward
					next.Send(msg);
				}
				else
				{
					// Respond
					msg.Type = LookupMessage.LookupType.Response;
					msg.SourceClient.Send(msg);
				}
			}
			else if (msg.Type == LookupMessage.LookupType.Response)
			{
				if (_lookupList.ContainsKey(msg.Id))
				{
					_lookupList[msg.Id].Result = destClient;
					_lookupList[msg.Id].Waiter.Set();
				}
			}
		}

		private void JoinReceive(JoinMessage msg, ClientInfo destClient)
		{
			if (msg.Type == JoinMessage.JoinType.Request)
			{
				if (msg.JoinInfo.Id == destClient.Id)
				{
					// Add source endpoints
					msg.JoinInfo.AddEndpoint(destClient.Endpoints);
				}

				ClientInfo next = _client.RoutingTable.FindNext(msg.JoinInfo.Id);
				if (next != null)
				{
					// Forward
					//Console.WriteLine(_client.Id + ": Forward JoinMessage");
					next.Send(msg);
				}

				// Add to local routing table
				_client.AddClient(msg.JoinInfo);
				_client.AddClient(destClient);

				// Send local routing table
				JoinMessage res = new JoinMessage();
				res.JoinInfo = msg.JoinInfo;
				res.Type = JoinMessage.JoinType.Response;
				res.Clients.AddRange(_client.RoutingTable.NodesFor(msg.JoinInfo));
				_client.Send(msg.JoinInfo, res);
				//Console.WriteLine(_client.Id + ": Send local routing table (" + res.Clients.Count + ")");
			}
			if (msg.Type == JoinMessage.JoinType.Response)
			{
				_client.AddClient(msg.Clients);
				Console.WriteLine(_client.Id + ": JoinMessage response from " + destClient.Id + "(" + msg.Clients.Count + ")");
			}
		}

		private void PingReceive(PingMessage msg, ClientInfo destClient)
		{
			if (msg.Type == PingMessage.MessageType.Request)
			{
				PingMessage res = new PingMessage();
				res.Type = PingMessage.MessageType.Response;
				res.Id = msg.Id;
				destClient.Send(res);
			}
			if (msg.Type == PingMessage.MessageType.Response)
			{
				_pingManager.ReceivePing(msg);
			}
		}

		public void Join()
		{
			if (_client.Bootstraps.Count > 0)
			{
				// Select random boostrap
				IPEndPoint endpoint = _client.Bootstraps[_rand.Next(_client.Bootstraps.Count)];

				//Console.WriteLine(_client.Id + ": Send join request");
				_client.Send(endpoint, new JoinMessage(new ClientInfo(_client, _client.Id)));
			}
		}

		private class LookupItem
		{
			public readonly ManualResetEvent Waiter = new ManualResetEvent(false);
			public ClientInfo Result;
		}

		public void SetClient(SpoonClient client)
		{
			_client = client;

			_client.OnStart += Join;
			_exchangeManager = new ExchangeManager(_client);
			_pingManager = new PingManager(_client);
		}

		public void RegisterHandler(MessageHandler handler)
		{
			handler.RegisterHandler(new MessageHandler.OnMessageReceiveHandler<PingMessage>(PingReceive));
			handler.RegisterHandler(new MessageHandler.OnMessageReceiveHandler<JoinMessage>(JoinReceive));
			handler.RegisterHandler(new MessageHandler.OnMessageReceiveHandler<LookupMessage>(LookupReceive));
			handler.RegisterHandler(new MessageHandler.OnMessageReceiveHandler<ExchangeMessage>(ExchangeReceive));
			handler.RegisterHandler(new MessageHandler.OnMessageReceiveHandler<InfoMessage>(InfoReceive));
		}

		private static void InfoReceive(InfoMessage msg, ClientInfo destClient)
		{ }

		private void ExchangeReceive(ExchangeMessage msg, ClientInfo destClient)
		{
			_exchangeManager.Receive(msg, destClient);
		}

		public void Dispose()
		{ }
	}
}